package cn.edu.lsnu.mbti.controller;

import cn.edu.lsnu.mbti.common.AuthAccess;
import cn.edu.lsnu.mbti.common.R;
import cn.edu.lsnu.mbti.dto.HotUniversity;
import cn.edu.lsnu.mbti.dto.UniversityDto;
import cn.edu.lsnu.mbti.entity.Page;
import cn.edu.lsnu.mbti.entity.PageUser;
import cn.edu.lsnu.mbti.entity.University;
import cn.edu.lsnu.mbti.entity.User;
import cn.edu.lsnu.mbti.service.*;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static cn.edu.lsnu.mbti.common.RedisConstants.*;

@RestController
@RequestMapping("index")
public class IndexController {

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private PageService pageService;
    @Resource
    private UniversityService universityService;
    @Resource
    private UserService userService;
    @Resource
    private PageUserService pageUserService;
    @Resource
    private RegionService regionService;
    @Resource
    private TypeService typeService;


    // 测试热部署
    @GetMapping("deploy")
    @AuthAccess
    public String test(){
        return "测试热部署";
    }

    /**
     * 定期为用户推荐接口
     * @param userId
     * @return
     */
    @GetMapping("revUniversityByBrowser")
    @AuthAccess
    public R<List<UniversityDto>> revUniversityByBrowser(@RequestParam String userId){
        //根据userId查询该用户的是否有mbti测试人格
        User presentUser = userService.getById(userId);
        String presentPersonalityId = presentUser.getPersonalityId();
        List<User> hasSamePersonalityUserList = new ArrayList<>();
        //确保当前用户至少在页面上的点击量为1
        if (pageUserCount(userId) == 0){
            return R.success(null);
        }
        //判断当前用户是否拥有人格结果
        if (StrUtil.isNotBlank(presentPersonalityId)){
            //查询数据库其他用户
            List<User> userList = userService.list();
            for (User user : userList) {
                String otherPersonalityId = user.getPersonalityId();
                if (StrUtil.isNotBlank(otherPersonalityId) && StrUtil.equals(presentPersonalityId,otherPersonalityId)
                        && !StrUtil.equals(user.getUsername(),presentUser.getUsername())){
                    //当前用户的测试人格与其他用户的测试人格一致时，收集其他用户
                    hasSamePersonalityUserList.add(user);
                }
            }
            if (hasSamePersonalityUserList.size() > 0){
                Map<String,Double> cosSimBaseOnUserMap = new HashMap<>();
                //当前用户的页面向量
                LambdaQueryWrapper<PageUser> wrapper1 = new LambdaQueryWrapper<>();
                wrapper1.eq(PageUser::getUserId, userId);
                List<PageUser> pageUserList = pageUserService.list(wrapper1);
                List<Long> presentUserCountList = pageUserList.stream().map(item -> item.getCount()).collect(Collectors.toList());
                //基于用户的协同过滤
                for (User user : hasSamePersonalityUserList) {
                    //其他用户的页面向量
                    LambdaQueryWrapper<PageUser> wrapper2 = new LambdaQueryWrapper<>();
                    wrapper2.eq(PageUser::getUserId, user.getId());
                    List<PageUser> pageOtherUserList = pageUserService.list(wrapper2);
                    List<Long> otherUserCountList = pageOtherUserList.stream().map(item -> item.getCount()).collect(Collectors.toList());
                    //计算当前用户与其余用户在页面上点击的余弦相似度
                    double cosSimilarity = similar(presentUserCountList, otherUserCountList);
                    cosSimBaseOnUserMap.put(user.getId(),cosSimilarity);
                }
                //得到了当前用户与其他用户的相似度，这里借助redis中的zset来存入cosSimBaseOnUserMap
                Set<String> cosSimBaseOnUserMapKeySet = cosSimBaseOnUserMap.keySet();
                for (String key : cosSimBaseOnUserMapKeySet) {
                    redisTemplate.opsForZSet().add(COS_USER_KEY,key,cosSimBaseOnUserMap.get(key));
                    redisTemplate.expire(COS_USER_KEY,3600,TimeUnit.SECONDS);
                }
                //获取redis中的COS_USER_KEY 排序
                List<String> redisUserIdList = new ArrayList<>();
                Set<ZSetOperations.TypedTuple<String>> tuples =
                        redisTemplate.opsForZSet().reverseRangeWithScores(COS_USER_KEY, 0, -1);
                Iterator<ZSetOperations.TypedTuple<String>> iterator = tuples.iterator();
                while (iterator.hasNext()){
                    ZSetOperations.TypedTuple<String> tuple = iterator.next();
                    String redisUserId = tuple.getValue();
                    redisUserIdList.add(redisUserId);
                }
                //获取余弦值最高的userId
                String highCosUserId = redisUserIdList.get(0);
                //根据userId查询在pageUser中点击的院校
                LambdaQueryWrapper<PageUser> pageUserWrapper = new LambdaQueryWrapper<>();
                pageUserWrapper.eq(PageUser::getUserId,highCosUserId);
                List<PageUser> cosPageUserList = pageUserService.list(pageUserWrapper);
                //在根据pageId来查询院校
                List<University> universityList = cosPageUserList.stream().map(obj -> {
                    Page page = pageService.getById(obj.getPageId());
                    University university = universityService.getById(page.getRouteId());
                    return university;
                }).collect(Collectors.toList());
                //对象复制
                List<UniversityDto> universityDtoList = convertToUniversityDtoList(universityList);
                return R.success(universityDtoList);
            }
        }
        //基于物品的协同过滤
        List<University> universityList = itemCF(userId);
        List<UniversityDto> universityDtoList = convertToUniversityDtoList(universityList);

        return R.success(universityDtoList);
    }


    /**
     * 查询热度院校接口
     * @return
     */
    @GetMapping("hotUniversity")
    @AuthAccess
    public R<List<HotUniversity>> hotUniversity() throws InterruptedException {
//        Thread.sleep(50);
        List<HotUniversity> hotUniversityList = new ArrayList<>();
        //1.获取当前小时的key
        Long hour = System.currentTimeMillis() / (1000 * 60 * 60);

        //2.从redis中查询热点院校数据
        Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(
                HOT_UNIVERSITY_KEY + String.valueOf(hour), 0, -1);
        if (tuples.size() > 0){
            Iterator<ZSetOperations.TypedTuple<String>> iterator = tuples.iterator();
            int flag = 0;
            while (iterator.hasNext()){
                ZSetOperations.TypedTuple<String> tuple = iterator.next();
                HotUniversity hotUniversity = new HotUniversity(UUID.randomUUID().toString(true), tuple.getValue(),tuple.getScore());
                hotUniversityList.add(hotUniversity);
                flag++;
                if (flag > 5) break;
            }

            //3.返回
            return R.success(hotUniversityList);
        }
        //为空从数据库中查询然后添加到redis中
        //获取热度最高的page （院校）
        LambdaQueryWrapper<Page> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(Page::getAccessTotal);
        List<Page> pageList = pageService.list(wrapper);
        List<Page> finalPageList = new ArrayList<>();
        if (pageList != null && pageList.size() > 0) {
            for (int i = 0; i < pageList.size(); i++) {
                if (i == 5) {
                    break;
                }
                String id = pageList.get(i).getRouteId();
                University university = universityService.getById(id);
                //判断是否是院校
                if (ObjectUtil.isNotNull(university)) {
                    finalPageList.add(pageList.get(i));
                }
            }
        }
        for (Page page : finalPageList) {
            University university = universityService.getById(page.getRouteId());
            HotUniversity hotUniversity = new HotUniversity(UUID.randomUUID().toString(true), university.getName(),page.getAccessTotal().doubleValue());
            hotUniversityList.add(hotUniversity);
            redisTemplate.opsForZSet().add(HOT_UNIVERSITY_KEY + String.valueOf(hour),university.getName(),page.getAccessTotal().doubleValue());
            redisTemplate.expire(HOT_UNIVERSITY_KEY + String.valueOf(hour),HOT_UNIVERSITY_EXPIRE, TimeUnit.SECONDS);
        }
        return R.success(hotUniversityList);
    }

    //判断用户在页面的点击率至少要有1个
    public int pageUserCount(String userId){
        LambdaQueryWrapper<PageUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StrUtil.isNotBlank(userId),PageUser::getUserId,userId);
        List<PageUser> pageUserList = pageUserService.list(queryWrapper);

        return pageUserList.size();
    }

    //基于物品的协同过滤
    public List<University> itemCF(String userId){
        Map<String, List<PageUser>> pageMap = new HashMap<>();
        //pageId查询pageUser集合
        List<University> universityList = universityService.list();
        for (University university : universityList) {
            String routeId = university.getId();
            LambdaQueryWrapper<Page> pageWrapper = new LambdaQueryWrapper<>();
            pageWrapper.eq(Page::getRouteId, routeId);
            List<Page> pageList = pageService.list(pageWrapper);
            for (Page page : pageList) {
                String pageId = page.getId();
                LambdaQueryWrapper<PageUser> pageUserWrapper = new LambdaQueryWrapper<>();
                pageUserWrapper.eq(PageUser::getPageId, pageId);
                List<PageUser> pageUserList = pageUserService.list(pageUserWrapper);
                pageMap.put(pageId, pageUserList);
            }
        }
        //找出当前用户在页面中点击率最高的页面
        LambdaQueryWrapper<PageUser> pageUserWrapper2 = new LambdaQueryWrapper<>();
        pageUserWrapper2.eq(PageUser::getUserId, userId);
        pageUserWrapper2.orderByDesc(PageUser::getCount);
        List<PageUser> pageUserList = pageUserService.list(pageUserWrapper2);
        PageUser pageUser = pageUserList.get(0); //获取第一个页面
        String pageId = pageUser.getPageId();
        //删除点击率最高的页面
        pageMap.remove(pageId);
        //获取cosList集合
        List<Double> cosList = new ArrayList<>();
        Map<String,Double> cosMap = new HashMap<>();
        LambdaQueryWrapper<PageUser> pageUserWrapper3 = new LambdaQueryWrapper<>();
        pageUserWrapper3.eq(PageUser::getPageId, pageId);
        List<PageUser> pageUserList1 = pageUserService.list(pageUserWrapper3);
        List<Long> countList = pageUserList1.stream().map(obj -> obj.getCount()).collect(Collectors.toList());
        Set<String> set = pageMap.keySet();
        int i = 0;
        for (String s : set) {
            cosList.add(
                    similar(countList, pageMap.get(s).stream().map(obj -> obj.getCount()).collect(Collectors.toList()))
            );
            cosMap.put(s,cosList.get(i));
            i++;
        }
//        cosList.forEach(i -> System.out.println(i));
        //对cosList排序，取前五个
        //将其存入zset中
        Set<String> cosMapKeySet = cosMap.keySet();
        for (String key : cosMapKeySet) {
            redisTemplate.opsForZSet().add(COS_UNIVERSITY_KEY,key,cosMap.get(key));
        }
        redisTemplate.expire(COS_UNIVERSITY_KEY,3600, TimeUnit.SECONDS);

        //取相似度最高的前五个
        List<String> pageIdList = new ArrayList<>();
        Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(COS_UNIVERSITY_KEY, 0, 5);
        Iterator<ZSetOperations.TypedTuple<String>> iterator = tuples.iterator();
        while (iterator.hasNext()){
            ZSetOperations.TypedTuple<String> tuple = iterator.next();
            String id = tuple.getValue();
            pageIdList.add(id);
        }
        //查询前五个院校
        List<University> resultUniversityList = pageIdList.stream().map(id -> {
            Page page = pageService.getById(id);
            University university = universityService.getById(page.getRouteId());
            return university;
        }).collect(Collectors.toList());
        System.out.println(resultUniversityList);

        return resultUniversityList;
    }


    //计算余弦相似度
    public double similar(List<Long> aList, List<Long> bList) {
        if (aList.size() > bList.size()) {
            int temp = aList.size() - bList.size();
            for (int i = 0; i < temp; i++) {
                bList.add(0L);
            }
        } else if (aList.size() < bList.size()) {
            int temp = bList.size() - aList.size();
            for (int i = 0; i < temp; i++) {
                aList.add(0L);
            }
        }
        int size = aList.size();
        double nume = 0; //分子
        double a_den = 1; //分母不能为0 前一个
        double b_den = 1; //分母不能为0 后一个

        for (int i = 0; i < size; i++) {
            double av = aList.get(i).doubleValue();
            double bv = bList.get(i).doubleValue();

            nume = nume + av * bv;
            a_den = a_den + Math.pow(av, 2);
            b_den = b_den + Math.pow(bv, 2);
        }

        a_den = Math.sqrt(a_den);
        b_den = Math.sqrt(b_den);


        return nume / (a_den * b_den);
    }

    //将universityList转换为universityDtoList
    public List<UniversityDto> convertToUniversityDtoList(List<University> universityList){
        List<UniversityDto> universityDtoList = new ArrayList<>();
        universityDtoList = universityList.stream().map(item -> {
            //实例化universityDto
            UniversityDto dto = new UniversityDto();

            //对象复制
            BeanUtils.copyProperties(item, dto);

            //为dto设置没有封装的regionName
            if (StrUtil.isNotBlank(dto.getRegionId())){
                dto.setRegionName(regionService.getById(dto.getRegionId()).getName());
            }

            //设置typeName
            if(StrUtil.isNotBlank(dto.getTypeId())){
                dto.setTypeName(typeService.getById(dto.getTypeId()).getName());
            }

            return dto;

        }).collect(Collectors.toList());

        return universityDtoList;
    }
}
